mod ffi;

pub type Result<T> = std::result::Result<T, Error>;

custom_error::custom_error! {pub Error
    {info:String} = "{info}",
}

pub mod client {
    use cxx::UniquePtr;

    use super::*;

    pub struct Client {
        inner: UniquePtr<ffi::client::Client>,
    }

    impl Client {
        pub fn new(host: &str, port: u16, worker_threads: u32) -> Client {
            Self {
                inner: ffi::client::new_client(host, port, worker_threads),
            }
        }

        pub fn set_timeout(&mut self, milliseconds: u32) {
            self.inner.pin_mut().set_timeout(milliseconds);
        }

        pub fn get_world(&self) -> Result<World> {
            self.inner
                .get_world()
                .map(|inner| World { inner })
                .map_err(|e| Error {
                    info: format!("{}", e),
                })
        }

        pub fn load_world(&mut self, map_name: &str, reset_settings: bool) -> Result<World> {
            self.inner
                .pin_mut()
                .load_world(map_name, reset_settings)
                .map(|inner| World { inner })
                .map_err(|e| Error {
                    info: format!("{}", e),
                })
        }

        pub fn generate_open_drive_world(
            &mut self,
            opendrive: &str,
            vertex_distance: f64,
            max_road_length: f64,
            wall_height: f64,
            additional_width: f64,
            smooth_junctions: bool,
            enable_mesh_visibility: bool,
            enable_pedestrian_navigation: bool,
            reset_settings: bool,
        ) -> Result<World> {
            self.inner
                .pin_mut()
                .generate_open_drive_world(
                    opendrive,
                    vertex_distance,
                    max_road_length,
                    wall_height,
                    additional_width,
                    smooth_junctions,
                    enable_mesh_visibility,
                    enable_pedestrian_navigation,
                    reset_settings,
                )
                .map(|inner| World { inner })
                .map_err(|e| Error {
                    info: format!("{}", e),
                })
        }

        pub fn reload_world(&mut self, reset_settings: bool) -> Result<World> {
            self.inner
                .pin_mut()
                .reload_world(reset_settings)
                .map(|inner| World { inner })
                .map_err(|e| Error {
                    info: format!("{}", e),
                })
        }
    }

    pub struct World {
        inner: UniquePtr<ffi::client::World>,
    }

    impl World {
        pub fn get_snapshot(&self) -> Result<WorldSnapshot> {
            self.inner
                .get_snapshot()
                .map(|inner| WorldSnapshot { inner })
                .map_err(|e| Error {
                    info: format!("{}", e),
                })
        }

        pub fn get_map(&self) -> Result<Map> {
            self.inner
                .get_map()
                .map(|inner| Map { inner })
                .map_err(|e| Error {
                    info: format!("{}", e),
                })
        }

        pub fn get_actor(&self, id: u32) -> Result<Option<Actor>> {
            self.inner
                .get_actor(id)
                .map(|inner| {
                    if !inner.is_null() {
                        Some(Actor { inner })
                    } else {
                        None
                    }
                })
                .map_err(|e| Error {
                    info: format!("{}", e),
                })
        }

        pub fn spawn_vehicle(
            &mut self,
            vehicle_type: &str,
            location_xyz: (f32, f32, f32),
            rotation_pyr: (f32, f32, f32),
        ) -> Result<Vehicle> {
            self.inner
                .pin_mut()
                .spawn_vehicle(
                    vehicle_type,
                    location_xyz.0,
                    location_xyz.1,
                    location_xyz.2,
                    rotation_pyr.0,
                    rotation_pyr.1,
                    rotation_pyr.2,
                )
                .map(|inner| Vehicle { inner })
                .map_err(|e| Error {
                    info: format!("{}", e),
                })
        }

        pub fn get_vehicle(&self, id: u32) -> Result<Option<Vehicle>> {
            self.inner
                .get_vehicle(id)
                .map(|inner| {
                    if !inner.is_null() {
                        Some(Vehicle { inner })
                    } else {
                        None
                    }
                })
                .map_err(|e| Error {
                    info: format!("{}", e),
                })
        }

        pub fn get_vehicle_by_role(&self, role_name: &str) -> Result<Option<Vehicle>> {
            self.inner
                .get_vehicle_by_role(role_name)
                .map(|inner| {
                    if !inner.is_null() {
                        Some(Vehicle { inner })
                    } else {
                        None
                    }
                })
                .map_err(|e| Error {
                    info: format!("{}", e),
                })
        }

        pub fn create_gnss(
            &mut self,
            parent_id: u32,
            location_xyz: (f32, f32, f32),
            rotation_pyr: (f32, f32, f32),
            sensor_tick: f32,
        ) -> Result<GNSS> {
            self.inner
                .pin_mut()
                .create_gnss(
                    parent_id,
                    location_xyz.0,
                    location_xyz.1,
                    location_xyz.2,
                    rotation_pyr.0,
                    rotation_pyr.1,
                    rotation_pyr.2,
                    sensor_tick,
                )
                .map_err(|e| Error {
                    info: format!("{}", e),
                })
                .and_then(|inner| {
                    if !inner.is_null() {
                        Ok(GNSS { inner })
                    } else {
                        Err(Error {
                            info: "Parent not find".to_string(),
                        })
                    }
                })
        }

        pub fn create_camera(
            &mut self,
            parent_id: u32,
            location_xyz: (f32, f32, f32),
            rotation_pyr: (f32, f32, f32),
            fov: f32,
            image_size_x: u32,
            image_size_y: u32,
            iso: f32,
            gamma: f32,
            shutter_speed: f32,
            sensor_tick: f32,
        ) -> Result<Camera> {
            self.inner
                .pin_mut()
                .create_camera(
                    parent_id,
                    location_xyz.0,
                    location_xyz.1,
                    location_xyz.2,
                    rotation_pyr.0,
                    rotation_pyr.1,
                    rotation_pyr.2,
                    fov,
                    image_size_x,
                    image_size_y,
                    iso,
                    gamma,
                    shutter_speed,
                    sensor_tick,
                )
                .map_err(|e| Error {
                    info: format!("{}", e),
                })
                .and_then(|inner| {
                    if !inner.is_null() {
                        Ok(Camera { inner })
                    } else {
                        Err(Error {
                            info: "Parent not find".to_string(),
                        })
                    }
                })
        }

        pub fn create_collision_detector(
            &mut self,
            parent_id: u32,
            location_xyz: (f32, f32, f32),
            rotation_pyr: (f32, f32, f32),
        ) -> Result<CollisionDetector> {
            self.inner
                .pin_mut()
                .create_collision_detector(
                    parent_id,
                    location_xyz.0,
                    location_xyz.1,
                    location_xyz.2,
                    rotation_pyr.0,
                    rotation_pyr.1,
                    rotation_pyr.2,
                )
                .map_err(|e| Error {
                    info: format!("{}", e),
                })
                .and_then(|inner| {
                    if !inner.is_null() {
                        Ok(CollisionDetector { inner })
                    } else {
                        Err(Error {
                            info: "Parent not find".to_string(),
                        })
                    }
                })
        }

        pub fn create_lane_invasion_detector(
            &mut self,
            parent_id: u32,
            location_xyz: (f32, f32, f32),
            rotation_pyr: (f32, f32, f32),
        ) -> Result<LaneInvasionDetector> {
            self.inner
                .pin_mut()
                .create_lane_invasion_detector(
                    parent_id,
                    location_xyz.0,
                    location_xyz.1,
                    location_xyz.2,
                    rotation_pyr.0,
                    rotation_pyr.1,
                    rotation_pyr.2,
                )
                .map_err(|e| Error {
                    info: format!("{}", e),
                })
                .and_then(|inner| {
                    if !inner.is_null() {
                        Ok(LaneInvasionDetector { inner })
                    } else {
                        Err(Error {
                            info: "Parent not find".to_string(),
                        })
                    }
                })
        }

        pub fn destroy_actor_by_id(&mut self, id: u32) -> Result<bool> {
            self.inner
                .pin_mut()
                .destroy_actor_by_id(id)
                .map_err(|e| Error {
                    info: format!("{}", e),
                })
        }
    }

    pub struct WorldSnapshot {
        inner: UniquePtr<ffi::client::WorldSnapshot>,
    }

    impl WorldSnapshot {
        pub fn get_running_time(&self) -> u64 {
            self.inner.get_running_time()
        }

        pub fn get_system_time(&self) -> u64 {
            self.inner.get_system_time()
        }

        pub fn find_actor_snapshot(&self, id: u32) -> Option<ActorSnapshot> {
            let inner = self.inner.find_actor_snapshot(id);
            if !inner.is_null() {
                Some(ActorSnapshot { inner })
            } else {
                None
            }
        }

        pub fn get_actor_ids(&self) -> Vec<u32> {
            self.inner.get_actor_ids()
        }
    }

    pub struct Map {
        inner: UniquePtr<ffi::client::Map>,
    }

    impl Map {
        pub fn get_name(&self) -> String {
            self.inner.get_name()
        }
    }

    pub struct LaneInvasionDetector {
        inner: UniquePtr<ffi::client::LaneInvasionDetector>,
    }

    impl LaneInvasionDetector {
        pub fn get_id(&self) -> u32 {
            self.inner.get_id()
        }

        pub fn get_event(&mut self) -> Option<LaneInvasionEvent> {
            let event = self.inner.pin_mut().get_event();
            if !event.is_null() {
                Some(LaneInvasionEvent { inner: event })
            } else {
                None
            }
        }
    }

    pub struct LaneInvasionEvent {
        inner: UniquePtr<ffi::client::LaneInvasionEvent>,
    }

    impl LaneInvasionEvent {
        pub fn get_timestamp(&self) -> u64 {
            self.inner.get_timestamp()
        }

        pub fn get_lane_change(&self) -> Vec<i32> {
            self.inner.get_lane_change()
        }
    }

    pub struct CollisionDetector {
        inner: UniquePtr<ffi::client::CollisionDetector>,
    }

    impl CollisionDetector {
        pub fn get_id(&self) -> u32 {
            self.inner.get_id()
        }

        pub fn get_event(&mut self) -> Option<CollisionEvent> {
            let event = self.inner.pin_mut().get_event();
            if !event.is_null() {
                Some(CollisionEvent { inner: event })
            } else {
                None
            }
        }
    }

    pub struct CollisionEvent {
        inner: UniquePtr<ffi::client::CollisionEvent>,
    }

    impl CollisionEvent {
        pub fn get_timestamp(&self) -> u64 {
            self.inner.get_timestamp()
        }

        pub fn get_other_actor(&self) -> Actor {
            Actor {
                inner: self.inner.get_other_actor(),
            }
        }

        pub fn get_normal_impulse(&self) -> (f32, f32, f32) {
            let r = self.inner.get_normal_impulse();
            (r[0], r[1], r[2])
        }
    }

    pub struct Camera {
        inner: UniquePtr<ffi::client::Camera>,
    }

    impl Camera {
        pub fn get_id(&self) -> u32 {
            self.inner.get_id()
        }

        pub fn get_image(&mut self) -> Option<Image> {
            let image = self.inner.pin_mut().get_image();
            if !image.is_null() {
                Some(Image { inner: image })
            } else {
                None
            }
        }
    }

    pub struct Image {
        inner: UniquePtr<ffi::client::Image>,
    }

    impl Image {
        pub fn get_timestamp(&self) -> u64 {
            self.inner.get_timestamp()
        }

        pub fn get_width(&self) -> u32 {
            self.inner.get_width()
        }

        pub fn get_height(&self) -> u32 {
            self.inner.get_height()
        }

        pub fn get_fov_angle(&self) -> f32 {
            self.inner.get_fov_angle()
        }

        pub fn get_bgra_pixels<'a>(&'a self) -> &'a [u8] {
            self.inner.get_bgra_pixels()
        }
    }

    pub struct GNSS {
        inner: UniquePtr<ffi::client::GNSS>,
    }

    impl GNSS {
        pub fn get_id(&self) -> u32 {
            self.inner.get_id()
        }

        pub fn get_localization(&mut self) -> Option<Localization> {
            let localization = self.inner.pin_mut().get_localization();
            if !localization.is_null() {
                Some(Localization {
                    inner: localization,
                })
            } else {
                None
            }
        }
    }

    pub struct Localization {
        inner: UniquePtr<ffi::client::Localization>,
    }

    impl Localization {
        pub fn get_timestamp(&self) -> u64 {
            self.inner.get_timestamp()
        }

        pub fn get_lla(&self) -> (f64, f64, f64) {
            let r = self.inner.get_lla();
            (r[0], r[1], r[2])
        }

        pub fn get_location(&self) -> (f32, f32, f32) {
            let r = self.inner.get_location();
            (r[0], r[1], r[2])
        }

        pub fn get_rotation(&self) -> (f32, f32, f32) {
            let r = self.inner.get_rotation();
            (r[0], r[1], r[2])
        }
    }

    pub struct Vehicle {
        inner: UniquePtr<ffi::client::Vehicle>,
    }

    impl Vehicle {
        pub fn get_id(&self) -> u32 {
            self.inner.get_id()
        }

        pub fn get_type_id(&self) -> String {
            self.inner.get_type_id()
        }

        pub fn destroy(&mut self) -> Result<bool> {
            self.inner.pin_mut().destroy().map_err(|e| Error {
                info: format!("{}", e),
            })
        }

        pub fn enable_carsim(&mut self, simfile_path: &str) -> Result<()> {
            self.inner
                .pin_mut()
                .enable_carsim(simfile_path)
                .map_err(|e| Error {
                    info: format!("{}", e),
                })
        }

        pub fn set_transform(
            &mut self,
            location_xyz: (f32, f32, f32),
            rotation_pyr: (f32, f32, f32),
        ) -> Result<()> {
            self.inner
                .pin_mut()
                .set_transform(
                    location_xyz.0,
                    location_xyz.1,
                    location_xyz.2,
                    rotation_pyr.0,
                    rotation_pyr.1,
                    rotation_pyr.2,
                )
                .map_err(|e| Error {
                    info: format!("{}", e),
                })
        }

        pub fn apply_control(&mut self, control: &Control) {
            self.inner.pin_mut().apply_control(
                control.throttle,
                control.steer,
                control.brake,
                control.hand_brake,
                control.reverse,
                control.manual_gear_shift,
                control.gear,
            )
        }
    }

    pub struct Actor {
        inner: UniquePtr<ffi::client::Actor>,
    }

    impl Actor {
        pub fn get_id(&self) -> u32 {
            self.inner.get_id()
        }

        pub fn get_type_id(&self) -> String {
            self.inner.get_type_id()
        }

        pub fn destroy(&mut self) -> Result<bool> {
            self.inner.pin_mut().destroy().map_err(|e| Error {
                info: format!("{}", e),
            })
        }

        pub fn get_semantic_tags(&self) -> Vec<u8> {
            self.inner.get_semantic_tags()
        }

        pub fn get_bounding_box(&self) -> BoundingBox {
            let bbox = self.inner.get_bounding_box();

            BoundingBox {
                location_xyz: (bbox[0], bbox[1], bbox[2]),
                rotation_pyr: (bbox[3], bbox[4], bbox[5]),
                extent_xyz: (bbox[6], bbox[7], bbox[8]),
            }
        }
    }

    #[derive(Clone, Debug)]
    pub struct BoundingBox {
        pub location_xyz: (f32, f32, f32),
        pub rotation_pyr: (f32, f32, f32),
        pub extent_xyz: (f32, f32, f32),
    }

    pub struct ActorSnapshot {
        inner: UniquePtr<ffi::client::ActorSnapshot>,
    }

    impl ActorSnapshot {
        pub fn get_id(&self) -> u32 {
            self.inner.get_id()
        }

        pub fn get_location(&self) -> (f32, f32, f32) {
            let r = self.inner.get_location();
            (r[0], r[1], r[2])
        }

        pub fn get_rotation(&self) -> (f32, f32, f32) {
            let r = self.inner.get_rotation();
            (r[0], r[1], r[2])
        }

        pub fn get_velocity(&self) -> (f32, f32, f32) {
            let r = self.inner.get_velocity();
            (r[0], r[1], r[2])
        }

        pub fn get_angular_velocity(&self) -> (f32, f32, f32) {
            let r = self.inner.get_angular_velocity();
            (r[0], r[1], r[2])
        }

        pub fn get_acceleration(&self) -> (f32, f32, f32) {
            let r = self.inner.get_acceleration();
            (r[0], r[1], r[2])
        }

        pub fn get_control(&self) -> Control {
            let control = self.inner.get_control();

            Control {
                throttle: control.get_throttle(),
                steer: control.get_steer(),
                brake: control.get_brake(),
                hand_brake: control.get_hand_brake(),
                reverse: control.get_reverse(),
                manual_gear_shift: control.get_manual_gear_shift(),
                gear: control.get_gear(),
            }
        }

        pub fn get_speed_limit(&self) -> f32 {
            self.inner.get_speed_limit()
        }

        pub fn has_traffic_light(&self) -> bool {
            self.inner.has_traffic_light()
        }

        pub fn get_traffic_light_id(&self) -> u32 {
            self.inner.get_traffic_light_id()
        }

        pub fn get_traffic_light_state(&self) -> i32 {
            self.inner.get_traffic_light_state()
        }
    }

    #[derive(Clone, Debug)]
    pub struct Control {
        pub throttle: f32,
        pub steer: f32,
        pub brake: f32,
        pub hand_brake: bool,
        pub reverse: bool,
        pub manual_gear_shift: bool,
        pub gear: i32,
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test() {
        let mut client = client::Client::new("127.0.0.1", 2000u16, 0u32);
        client.set_timeout(5000u32);

        let mut world = client.get_world().unwrap();
        let mut vehicle = world
            .spawn_vehicle("tesla.model3", 20.4, 5.8, 1.0, 0.0, -63.0, 0.0)
            .unwrap();
        std::thread::sleep(std::time::Duration::from_millis(2000));
        vehicle.destroy().unwrap();
    }
}
